.. _Неизменняемость:

Неизменняемость
===============

.. index::
    неизменяемость
    immutability

Понятия представлены переменными программы. Взаимодействие понятий
обычно достигается с помощью выражений, которые изменяют значения переменных,
представляющих эти понятия. Например, следующий код изменяет некоторые переменные,
представляющие покупку:


.. code-block:: d

    totalPrice = calculateAmount(itemPrices);
    moneyInWallet -= totalPrice;
    moneyAtMerchant += totalPrice;

Изменение значения переменной называется модификацией или мутацией переменной.
Запрет на мутацию переменной называется неизменяемостью.
Поскольку мутация необходима для решения большинства задач, намеренное запрещение ее может быть
неинтуитивным, но является мощной и полезной функцией. Концепция
неизменяемости основана на опыте, накопленном сообществом программистов в целом.
в целом: Неизменяемость помогает в обеспечении корректности и сопровождаемости программ.
Эта идея настолько сильна, что некоторые языки функционального программирования запрещают
мутации вообще.

Некоторые преимущества неизменяемости заключаются в следующем:

* Некоторые понятия неизменяемы по определению. Например, в неделе всегда
  семь дней в неделе, математическая константа пи никогда не меняется, список
  естественных языков, поддерживаемых программой, может быть фиксированным (например, только английский и
  турецкий) и т. д.
* Даже если они не представляют неизменяемые понятия, некоторые переменные не
  не предназначены для изменения после их инициализации; так что это может быть ошибкой программиста.
  Ошибка программиста. Например, totalPrice в приведенном выше коде, скорее всего, не должна
  мутировать после присвоения ей начального значения.
* Определяя только некоторые из своих параметров как изменяемые, функция может диктовать
  какие из этих параметров будут использоваться как есть в качестве входных данных, а какие
  какие параметры будут изменены в качестве побочных эффектов.

Неизменяемость настолько распространена в программировании в целом и широко принята программистами на языке D,
что следующие курьезы принимаются как следствия:

* Как показывают программы, которые мы писали до сих пор, неизменяемость не является
  абсолютно необходимой.
* Концепция неизменяемости выражается в D ключевым слово const (сокращение от "constant")
  и ключевым словом immutable. Хотя эти два слова имеют одинаковое значение в
  английском языке, их значение в программах различно, и они
  иногда несовместимы. (Как и inout (:ref:`Параметры функций<глава_35_start>`) и shared (:ref:`Совместное использование данных (Concurrency)<Совместное использование данных (Concurrency)>`,
  const и immutable - это классификаторы типов.)
* Оба термина "неизменяемая переменная" и "постоянная переменная"
  бессмысленны, когда слово "переменная" понимается буквально как означающее что-то, что
  меняется. В более широком смысле слово "переменная" часто понимается как означающее
  любую концепцию программы, как изменяемую, так и неизменяемую.
* В качестве примера самореализующегося пророчества функции вынуждены соблюдать
  неизменность (вынуждены быть константно-корректными) и, как
  следствие, становятся более полезными.
  (Это принудительное соблюдение константно-корректности иногда описывается как вирусное заболевание.)

.. index::
    неизменяемые переменные

Неизменяемые переменные
------------------------

Существует три способа определения переменных, которые нельзя изменять

**enum constants**

Ранее в главе enum (стр. 132) мы видели, что enum определяет именованные
постоянные значения:
Поскольку их значения могут быть определены во время компиляции, также enum переменные могут быть
инициализированы более сложными выражениями, включая возвращаемые значения
функций:

.. code-block:: d

    int totalLines()
    {
        return 42;
    }

    int totalColumns()
    {
        return 7;
    }

    string name()
    {
        return "list";
    }

    void main()
    {
        enum fileName = name() ~ ".txt";
        enum totalSquares = totalLines() * totalColumns();
    }


Общей функцией, обеспечивающей такую инициализацию, является выполнение функции во время компиляции
(CTFE), которую мы рассмотрим в следующей главе (:ref:`Enumeration<Enumeration>`).
Как и ожидалось, значения констант enum не могут быть изменены:

.. code-block:: d

    ++totalSquares; // ← ошибка компиляции

Хотя это очень эффективный способ представления неизменяемых значений, enum можно
использовать только со значениями во время компиляции.
Константа enum является явной константой, что означает, что программа компилируется
так, как если бы каждое упоминание этой константы было заменено ее значением. В качестве
примера давайте рассмотрим следующее определение enum и два выражения, которые
его используют:

.. code-block:: d

    enum i = 42;
    writeln(i);
    foo(i);

Приведенный выше код является точным эквивалентом приведенного ниже, где каждое использование i
заменяется его значением, равным 42:

.. code-block:: d

    writeln(42);
    foo(42);

Хотя такая замена имеет смысл для простых типов, таких как int, и не имеет никакого
значения для результирующей программы, константы enum могут привести к скрытым затратам, когда
они используются для массивов или ассоциативных массивов:

.. code-block:: d

    enum a = [ 42, 100 ];
    writeln(a);
    foo(a);

После замены a на его значение эквивалентный код, который будет
компилироваться компилятором, будет выглядеть следующим образом:

.. code-block:: d

    writeln([ 42, 100 ]); // массив создаётся в runtime
    foo([ 42, 100 ]);     // еще один массив создаётся в runtime

Скрытая стоимость здесь заключается в том, что для
двух приведенных выше выражений будут созданы два отдельных массива. По этой причине имеет смысл определять массивы и
ассоциативные массивы как неизменяемые переменные, если они будут упоминаться
в программе более одного раза.

**const переменные**

Как и enum, это ключевое слово указывает, что значение переменной никогда не изменится.
В отличие от enum, переменная const - это фактическая переменная с адресом памяти, а
переменные const обычно инициализируются во время выполнения программы.
Компилятор не позволяет изменять переменную const:

.. code-block:: d

    const half = total / 2;
    half = 10; // ← ошибка компиляции

Следующая программа использует как enum, так и const. Программа просит пользователя
угадать число, которое было выбрано случайным образом. Поскольку случайное число
не может быть определено во время компиляции, оно не может быть определено как enum. Тем не менее, поскольку
случайно выбранное значение никогда не должно изменяться после принятия решения,
целесообразно указать эту переменную как const.
Программа использует функцию reading(), которая была определена в
предыдущей главе:

.. code-block:: d

    import std.stdio;
    import std.random;

    int readInt(string message)
    {
        int result;
        write(message, "? ");
        readf(" %s", &result);
        return result;
    }

    void main()
    {
        enum min = 1;
        enum max = 10;
        const number = uniform(min, max + 1);
        writefln("I am thinking of a number between %s and %s.", min, max);
        auto isCorrect = false;
        while (!isCorrect)
        {
            const guess = readInt("What is your guess");
            isCorrect = (guess == number);
        }

        writeln("Correct!");
    }

Наблюдения:

* min и max являются неотъемлемыми частями поведения этой программы, и их значения
  известны во время компиляции. По этой причине они определены как
  константы перечисления.
* число указано как const, поскольку было бы неуместно изменять его
  после инициализации во время выполнения. Аналогично для каждого пользовательского предположения: после прочтения
  предположение не должно изменяться.
* Типы этих переменных явно не указаны. Как и при использовании
  auto, enum и т.д.; тип переменной const может быть выведен из
  выражения в правой части.

Хотя нет необходимости указывать тип полностью, const обычно принимает
фактический тип в круглых скобках, например const(int). Выходные данные следующей
программы демонстрируют, что полные имена типов трех переменных
на самом деле совпадают:

.. code-block:: d

    import std.stdio;

    void main()
    {
        const inferredType = 0;
        const int explicitType = 1;
        const(int) fullType = 2;
        writeln(typeof(inferredType).stringof);
        writeln(typeof(explicitType).stringof);
        writeln(typeof(fullType).stringof);
    }

Фактическое имя типа включает const:

.. code-block:: d

    const(int)
    const(int)
    const(int)

Использование круглых скобок имеет значение и указывает, какие части типа являются
неизменяемыми. Мы увидим это ниже при обсуждении неизменяемости всего
фрагмента в сравнении с его элементами.

**неизменяемые переменные**

При определении переменных ключевое слово immutable имеет тот же эффект, что и const.
неизменяемые переменные не могут быть изменены:

.. code-block:: d

    immutable half = total / 2;
    half = 10; // ← ошибка компиляции

Если другие части программы не требуют, чтобы переменная была неизменяемой, неизменяемые
переменные могут быть определены либо как const, либо как immutable. Когда функция
конкретно требует, чтобы параметр был неизменяемым, тогда переменная, соответствующая этому параметру, должна быть
определена как неизменяемая. Мы увидим это ниже.

.. index::
    параметры

Параметры
---------

Как мы увидим в следующих двух главах, функции могут изменять свои параметры.
Например, они могут изменять элементы фрагментов, которые передаются в качестве аргументов
этим функциям.
Как вы, наверное, помните из главы "Срезы и другие функции массива
" (:ref:`Фрагменты и прочие возможности массивов<Фрагменты и прочие возможности массивов>`), срезы не являются владельцами элементов, но предоставляют к ним доступ. В данный момент времени может быть
более одного среза, который предоставляет доступ к одним и тем же элементам.
Хотя примеры в этом разделе посвящены только срезам, этот раздел
применим также к ассоциативным массивам и классам, поскольку они тоже являются ссылочными
типами.

Срез, передаваемый в качестве аргумента функции, не является срезом, с помощью которого
вызывается функция. Аргумент является копией переменной среза. (Копируется только переменная среза, а не элементы.)

.. code-block:: d

    import std.stdio;

    void main()
    {
        int[] slice = [10, 20, 30, 40]; // 1
        halve(slice);
        writeln(slice);
    }

    void halve(int[] numbers)
    { // 2
        foreach (ref number; numbers)
        {
            number /= 2;
        }
    }

Когда выполнение программы переходит в функцию halve(), есть два фрагмента, которые
предоставляют доступ к одним и тем же четырем элементам:

#. Фрагмент с именем slice, определенный в main(), который передается в halve()
   в качестве параметра
#. Срез с именем numbers, который have() получает в качестве аргумента, который
   предоставляет доступ к тем же элементам, что и slice
   Поскольку оба среза ссылаются на одни и те же элементы и что мы используем ключевое слово ref в
   цикле foreach, значения элементов уменьшаются вдвое:

.. code-block:: console

    [5, 10, 15, 20]

Действительно, для функций полезно иметь возможность изменять элементы срезов,
которые передаются в качестве аргументов. Как мы видели в этом примере, некоторые функции
существуют именно для этой цели.
Компилятор не позволяет передавать переменные const в качестве аргументов таким
функциям:

.. code-block:: d

    const(int[]) slice = [ 10, 20, 30, 40 ];
    halve(slice); // ← ошибка компиляции

Ошибка компиляции указывает на то, что переменная типа const(int[]) не может быть
использована в качестве аргумента типа int[]:

.. code-block:: console

    Error: function deneme.halve (int[] numbers) is not callable using argument types (const(int[]))

.. index::
    константные параметры

**параметры const**

Важно и естественно, чтобы переменные const не передавались
таким функциям, как halve(), которые изменяют свои аргументы. Однако это было бы
ограничением, если бы они не могли быть переданы функциям, которые не намерены
их изменять, как функция print() в примере ниже:

.. code-block:: d

    import std.stdio;

    void main()
    {
        const(int[]) slice = [10, 20, 30, 40];
        print(slice); // ← compilation ERROR
    }

    void print(int[] slice)
    {
        writefln("%s elements: ", slice.length);
        foreach (i, element; slice)
        {
            writefln("%s: %s", i, element);
        }
    }


Вышеизложенное не имеет смысла, что фрагмент не может быть напечатан только
потому, что он является const. Правильный способ справиться с этой ситуацией - использовать
параметры const. Это называется корректировкой функции const.
((Это упомянутое выше самоисполняющееся пророчество, которое заставляет функции соблюдать неизменность.)
Ключевое слово const указывает, что переменная не изменяется посредством этой
конкретной ссылки (например, фрагмента) этой переменной. Указание параметра как const
гарантирует, что элементы среза не будут изменены внутри функции.
Как только функция print() предоставит эту гарантию, программа может быть скомпилирована:

.. code-block:: d

    // ...
    void print(const int[] slice)
    {
    // ...
    }

Эта гарантия отсутствия мутаций обеспечивает гибкость, поскольку позволяет передавать
изменяемые, константные и неизменяемые переменные в качестве аргументов:

.. code-block:: d

    int[] mutableSlice = [ 7, 8 ];
    print(mutableSlice); // компилируется
    const int[] slice = [ 10, 20, 30, 40 ];
    print(slice); // компилируется
    immutable int[] immSlice = [ 1, 2 ];
    print(immSlice); // компилируется

И наоборот, невозможность определить параметр как const, когда этот параметр не
изменен в функции, снижает применимость этой функции. Такие
функции не являются константно-корректными.
Еще одним преимуществом параметров const является предоставление полезной информации
программисту: знание того, что переменная не будет изменена при передаче
функции, облегчает понимание кода.
Тот факт, что параметры const могут принимать изменяемые, const и неизменяемые
переменные, имеет интересное следствие. Это объясняется в разделе "Должен ли
параметр быть постоянным или неизменяемым?" ниже.


.. index::
    спецификация параметра in

**спецификация параметра in**

Как мы увидим в следующей главе, in подразумевает const и более полезен с переключателем
‑preview=в командной строке. По этой причине я рекомендую in parameters
вместо const parameters.

**неизменяемые параметры**

Параметры const можно рассматривать как приветствуемые, поскольку они принимают изменяемые, const
и неизменяемые переменные в качестве аргументов.
Напротив, неизменяемые параметры являются избирательными, поскольку предъявляют строгие
требования: Аргумент должен быть неизменяемым. В то время как параметр const
сообщает "я не буду изменять", неизменяемый параметр добавляет "и вы
тоже не должны изменять".
Только неизменяемые переменные могут передаваться функциям в качестве их неизменяемых
параметров:

.. code-block:: d

    void func(immutable int[] slice)
    {
    // ...
    }

    void main()
    {
        immutable int[] immSlice = [ 1, 2 ];
        int[] slice = [ 8, 9 ];
        func(immSlice); // compiles
        func(slice); // ← compilation ERROR
    }

По этой причине спецификатор immutable следует использовать только тогда, когда это
требование действительно необходимо. Мы действительно использовали параметр immutable,
указанный косвенно через определенные типы строк. Об этом будет рассказано ниже.
Мы видели, что параметры, которые указаны как const или immutable,
обещают не изменять фактическую переменную, передаваемую в качестве аргумента. Это
актуально только для ссылочных типов, потому что только тогда есть фактическая переменная,
о неизменяемости которой можно говорить.
Ссылочные типы и типы значений будут рассмотрены в следующей главе. Среди
типы, которые мы видели до сих пор, только срезы и ассоциативные массивы являются ссылочными
типами; остальные являются типами значений.

**Должен ли параметр быть const или неизменяемым?**

*Примечание: Поскольку in подразумевает const, этот раздел также посвящен in.
Приведенные выше разделы могут создать впечатление, что, будучи более гибкими,
параметрам построения следует отдавать предпочтение перед неизменяемыми параметрами. Это не всегда
верно.*
const стирает информацию о том, была ли исходная переменная изменяемой,
const или неизменяемой. Эта информация скрыта даже от компилятора.
Следствием этого факта является то, что параметры const не могут передаваться в качестве
аргументов функциям, которые принимают неизменяемые параметры. Например, в
промежуточная функция foo(), приведенная ниже, не может передать свой параметр const в bar()
даже несмотря на то, что фактическая переменная, передаваемая через функции, изначально определена как
неизменяемая в main:

.. code-block:: d

    void main()
    {
        /* Оригинальная переменная immutable */
        immutable int[] slice = [10, 20, 30, 40];
        foo(slice);
    }

    // Функция, которая принимает свой параметр как const, чтобы  быть более полезной. */
    void foo(const int[] slice)
    {
        bar(slice); // ← ошибка компиляции
    }

    /* Функция, для которой требуется неизменяемый срез. */
    void bar(immutable int[] slice)
    {
        // ...
    }


функция bar() требует, чтобы параметр был неизменяемым. Однако неизвестно (в
общем случае), была ли исходная переменная, на которую ссылается параметр const функции foo(), неизменяемой или нет.
*Примечание: Из приведенного выше кода наблюдателю ясно, что исходная переменная в main()
является неизменяемой. Однако компилятор компилирует функции индивидуально, без учета
каждого места, из которого вызывается функция. Для компилятора параметр slice
функции foo() может ссылаться на изменяемую переменную или на неизменяемую.*

Решением было бы вызвать bar() с неизменяемой копией параметра:

.. code-block:: d

    void foo(const int[] slice)
    {
        bar(slice.idup);
    }

Хотя это заставило бы код скомпилироваться, это повлекло бы за собой затраты на копирование
фрагмента и его содержимого, что было бы расточительно в случае, когда исходная
переменная изначально была неизменяемой.
После этого анализа должно быть ясно, что всегда объявлять параметры как const
- не лучший подход в любой ситуации. В конце концов, если бы параметр foo()
был определен как неизменяемый, не было бы необходимости копировать его перед вызовом
bar():

.. code-block:: d

    void foo(immutable int[] slice)  // This time immutable
    {
        bar(slice); // Copying is not needed anymore
    }

Хотя код компилируется, определение параметра как неизменяемого сопряжено с аналогичными
затратами: на этот раз при вызове
foo() требуется неизменяемая копия исходной переменной, если эта переменная изначально не была неизменяемой:

.. code-block:: d

    foo(mutableSlice.idup);

Шаблоны могут помочь. (Мы познакомимся с шаблонами в последующих главах.) Хотя я не
ожидаю, что вы полностью поймете следующую функцию на данном этапе книги, я
представлю ее как решение этой проблемы. Следующий шаблон функции
foo() может быть вызван с изменяемыми, const и неизменяемыми переменными.
Параметр был бы скопирован только в том случае, если исходная переменная была изменяемой; копирование
не производилось бы, если бы она была неизменяемой:

.. code-block:: d

    import std.conv;
    // ...
    /* Поскольку это шаблон, функция foo() может вызываться как с изменяемыми, так и с неизменяемыми переменными. */
    void foo(T)(T[] slice)
    {
    /* 'to()' does not make a copy if the original variable is  already immutable. */
        bar(to!(immutable T[])(slice));
    }


Инициализация
-------------

Запрет мутации можно рассматривать как ограничение, когда начальные значения переменных
зависят от нетривиальных выражений. Например, содержимое массива fruits
ниже зависит от значения addCitrus, но код не удается скомпилировать, поскольку
переменная const:

.. code-block:: d

    const fruits = [ "apple", "pear" ];
    if (addCitrus)
    {
        fruits ~= [ "orange" ]; // ошибка компиляции
    }

Хотя создание переменной изменяемой, например, путем определения ее с помощью auto, позволило
бы скомпилировать код, все еще возможно определить ее как const, переместив код
инициализации в функцию:

.. code-block:: d

    bool addCitrus;
    string[] makeFruits()
    {
        auto result = ["apple", "pear"];
        if (addCitrus)
        {
            result ~= ["orange"];
        }
        return result;
    }

    void main()
    {
        const fruits = makeFruits();
    }


Обратите внимание, что локальный результирующий массив является изменяемым, но результаты остаются неизменными
(предположительно, по желанию программиста). Когда невозможно или
громоздко переместить код в именованную функцию, вместо этого можно использовать лямбда :ref:`Указатели на функции, делегаты и лямбда-выражения<глава_72_start>`

.. code-block:: d

    const fruits = {
    // Exactly the same code as 'makeFruits()'.
        auto result = ["apple", "pear"];
        if (addCitrus)
        {
            result ~= ["orange"];
        }
        return result;
    }();

Лямбда определяется выделенными фигурными скобками и выполняется
круглыми скобками в конце. Опять же, переменная fruits становится const по желанию.
D позволяет присваивать константные и неизменяемые переменные в специальных
блоках инициализации, называемых shared static this() (и static this()). Эти блоки предназначены для
инициализации переменных, определенных в области модуля (вне какой-либо функции).
Можно изменять постоянные и неизменяемые переменные в shared static this()
блоки:

.. code-block:: d

    immutable int[] i;
    shared static this()
    {
        // It is possible to mutate 'const' and 'immutable' module
        // variables in this block:
        i ~= 43;
        // The variables are still 'const' and 'immutable' for the
        // rest of the program.
    }


общие статические блоки this() выполняются до того, как программа начнет выполнять
тело функции main().

Неизменность фрагмента по сравнению с элементами
-------------------------------------------------

Мы видели выше, что тип среза const был напечатан как
const(int[]). Как указывают круглые скобки после const, константным является весь срез.
Такой срез не может быть изменен каким-либо образом: элементы не могут быть добавлены или
удалены, их значения не могут быть изменены, и фрагмент может не начать предоставлять
доступ к другому набору элементов:

.. code-block:: d

    const int[] slice = [ 1, 2 ];
    slice ~= 3; // ← ошибка компиляции
    slice[0] = 3; // ← ошибка компиляции
    slice.length = 1; // ← ошибка компиляции
    const int[] otherSlice = [ 10, 11 ];
    slice = otherSlice; // ← ошибка компиляции

Доведение неизменяемости до такой крайности может оказаться подходящим не в каждом случае.
В большинстве случаев важна неизменяемость самих элементов. Поскольку
срез - это всего лишь инструмент для доступа к элементам, не должно иметь значения, вносим ли мы изменения
в сам срез, пока элементы не изменяются. Это особенно верно в случаях,
которые мы видели до сих пор, когда функция получает копию самого среза.
Чтобы указать, что неизменяемыми являются только элементы, мы используем ключевое слово const
со скобками, которые заключают только тип элемента. Изменение кода
соответственно, неизменяемыми являются не только элементы, но и сам срез:

.. code-block:: d

    const(int)[] slice = [ 1, 2 ];
    slice ~= 3; // можем добавить элемент
    slice[0] = 3; // ← ошибка компиляции
    slice.length = 1; // можем удалить элемент
    const int[] otherSlice = [ 10, 11 ];
    slice = otherSlice; // можемоткрыть доступ к другим элементам

Хотя эти два синтаксиса очень похожи, они имеют разные значения. Подводя
итог:

.. code-block:: d

    const int[] a = [1];  /* Ни элементы, ни срез *не могут быть изменены */
    const(int[]) b = [1]; /* То же значение, что и выше */
    const(int)[] c = [1]; /* Значение элементы не могут быть изменены, но фрагмент может быть */

Это различие действовало в некоторых программах, которые мы написали до сих пор.
Как вы, возможно, помните, три строковых псевдонима подразумевают неизменяемость:

* string - это псевдоним для неизменяемого(char)[]
* wstring - это псевдоним для immutable(wchar)[]
* dstring - это псевдоним для immutable(dchar)[]

Аналогично, строковые литералы также являются неизменяемыми:

* Тип литерала "hello"c - string
* Тип литерала "hello"w равен wstring
* Тип литерала "hello"d равен dstring

Согласно этим определениям, cтроки  в языке D обычно представляют собой массивы неизменяемых
символов. (immutable characters)

**const и immutable являются транзитивными**

Как упоминалось в комментариях к коду фрагментов a и b выше, как эти фрагменты, так и
их элементы являются неизменяемыми.
Это верно также для структур (:ref:`Структуры<Структуры>`) и классов (:ref:`Классы<Классы>`), оба из которых
будут рассмотрены в последующих главах. Например, все члены
переменной const struct являются константными, а все члены неизменяемой переменной struct являются
неизменяемыми. (Аналогично для классов.)

.. index::
    .dup
    .idup

**.dup и .idup**

Могут быть несоответствия в неизменяемости, когда строки передаются функциям
в качестве параметров. Свойства .dup и .idup создают копии массивов с
желаемой изменяемостью:

* .dup создает изменяемую копию массива ("dup" означает "дублировать")
* .idup создает неизменяемую копию массива

Например, функцию, которая настаивает на неизменяемости параметра, возможно, придется
вызывать с неизменяемой копией изменяемой строки:

.. code-block:: d

    void foo(string s)
    {
    // ...
    }

    void main()
    {
        char[] salutation;
        foo(salutation); // ← ошибка компиляции
        foo(salutation.idup); // ← this compiles
    }

Рекомендации по использованию

* Как правило, предпочитайте неизменяемые переменные изменяемым.
* Определяйте постоянные значения как enum, если их значения могут быть вычислены во
  время компиляции. Например, постоянное значение секунд в минуту может быть enum:

.. code-block:: d

    enum int secondsPerMinute = 60;

Нет необходимости явно указывать тип, если он может быть автоматически выведен

.. code-block:: d

    enum secondsPerMinute = 60;

* Учитывайте скрытую стоимость массивов enum и ассоциативных массивов enum. Определите
  их как неизменяемые переменные, если они используются в программе более одного раза.
* Укажите переменные как const, если их значения никогда не изменятся, но не могут быть
  известны во время компиляции. Опять же, тип может быть определен логически:

.. code-block:: d

    const guess = readInt("What is your guess");

Если функция не изменяет параметр, укажите этот параметр, как in. Это
выразило бы намерение программиста и позволило бы передавать как изменяемые, так и неизменяемые
переменные в качестве аргументов:

.. code-block:: d

    void foo(in char[] s)
    {
    // ...
    }

    void main()
    {
        char[] mutableString;
        string immutableString;
        foo(mutableString); // ← компилируется
        foo(immutableString); // ← компилируется
    }


В отличие от большинства примеров в этой книге, всегда указывайте, какая часть
параметра должна быть неизменяемой:

.. code-block:: d

    // Только элементы не могут быть изменены (срез может быть)
    void print_1(const(int)[] slice)
    {
        // ...
    }

    // Ни переменная slice, ни элементы не могут быть изменены
    void print_2(const(int[]) slice)
    {
        // ...
    }

    // То же, что и print_2() (В отличие от большинства примеров в этой книге, избегайте этого стиля
    void print_3(const int[] slice)
    {
        // ...
    }


* Учтитывайте, что параметры const не могут быть переданы функциям, принимающим
  неизменяемые значения. Смотрите раздел "Должен ли параметр быть постоянным или неизменяемым?"
  выше.

* Если функция изменяет параметр, оставьте этот параметр изменяемым (in,
  const или immutable в любом случае не допускают изменений):

.. code-block:: d

    import std.stdio;

    void reverse(dchar[] s)
    {
        foreach (i; 0 .. s.length / 2)
        {
            immutable temp = s[i];
            s[i] = s[$ - 1 - i];
            s[$ - 1 - i] = temp;
        }
    }

    void main()
    {
        dchar[] salutation = "hello"d.dup;
        reverse(salutation);
        writeln(salutation);
    }

Вывод

.. code-block:: console

    olleh

Итоги
-----

* переменные enum представляют неизменяемые концепции, которые известны во время компиляции.
* массивы enum и ассоциативные массивы сопряжены с затратами на создание нового массива при
  каждом упоминании этой константы enum. Вместо этого предпочитайте неизменяемые массивы и
  ассоциативные массивы.
* Предпочитайте параметры in параметрам const.
* переменные const и immutable представляют неизменяемые понятия, которые могут быть
  известны во время выполнения ( ???? TODO или несколько неясно, у которых должно быть какое-то место в памяти, на которое мы можем ссылаться).
* константные параметры - это те, которые функции не изменяют. Изменяемые, const,
  и неизменяемые переменные могут передаваться в качестве аргументов параметров const.
* неизменяемые параметры - это те, которые функции специально требуют, чтобы они
  были такими. Только неизменяемые переменные могут передаваться в качестве аргументов неизменяемых
  параметров.
* const(int[]) указывает, что ни срез ни его элементы не могут быть
  изменены.
* const(int)[] указывает, что только элементы не могут быть изменены.
